#include <string.h>

#include "lib.h"

#include "pghand.h"
#include "jokcrdmg.h"
#include "tilecrdmg.h"

#define SELF	PGPokerHand
#define SUPER	Hand

// following macros deal with ranking the hand and the counts[] array
//
#define ACENDX				14
#define JOKERNDX			15
#define COUNTNDX(rank)		((rank) - 2)		// card rank to counts[] index
#define RANKNDX(countndx)	((int32)((countndx)+2))	// counts[] to card rank

#define H_GETSUIT_AT(i)		jokMgr()->getSuit(getCardAt(i))
#define H_GETRANK_AT(i)		jokMgr()->getRank(getCardAt(i))
#define H_ISJOKER_AT(i)		jokMgr()->isJoker(getCardAt(i))

#define HPTR_JOKMGR()		hand->jokMgr()
#define HPTR_MGR()			hand->getMgr()
#define HPTR_GETSUIT(c)		HPTR_MGR()->getSuit(c)
#define HPTR_GETRANK(c)		HPTR_MGR()->getRank(c)
#define HPTR_ISJOKER(c)		HPTR_JOKMGR()->isJoker(c)
#define HPTR_GETSUIT_AT(i)	HPTR_GETSUIT(hand->getCardAt(i))
#define HPTR_GETRANK_AT(i)	HPTR_GETRANK(hand->getCardAt(i))
#define HPTR_ISJOKER_AT(i)	HPTR_ISJOKER(hand->getCardAt(i))

static JokerCardMgr jokMgr;
static TilesCardMgr tileMgr;

PGHand*
PGHand::createHand(uint8 machid)
{
	switch (machid)
	{
		case 0x00:
			return new PGPokerHand(&jokMgr);
			break;

		case 0x01:
			return new PGTilesHand(&tileMgr);
			break;

		default:
			return 0;
	}
}


PGPokerHand::PGPokerHand(JokerCardMgr* Mgr)
	: PGHand(Mgr)
{
}

bool8
PGPokerHand::isSplitValid(int32 twocard[2])
{
	PGPokerHandRanks ranks(this, twocard);

	if (ranks.getLowRank() < ranks.getHighRank())
		return true;
	else if (ranks.getLowRank() > ranks.getHighRank())
		return false;
	else
	{
		int32 loHighCard, hiHighCard;

		loHighCard = ranks.getLowNthHighCard(0);
		hiHighCard = ranks.getHighNthHighCard(0);

		if (loHighCard < hiHighCard)
			return true;
		else if (loHighCard > hiHighCard)
			return false;
		else
		{
			loHighCard = ranks.getLowNthHighCard(1);
			hiHighCard = ranks.getHighNthHighCard(1);

			// *CAN* have twocard pair equal to fivecard pair and 3 singletons!
			//
			return (bool8)(loHighCard <= hiHighCard);
		}
	}
}

bool8
PGPokerHand::contains(int32 suit, int32 rank)
{
	int32 i;

	for (i = 0; i < getCount(); i++)
		if (H_GETSUIT_AT(i) == suit &&
			H_GETRANK_AT(i) == rank)
			return true;

	return false;
}

int32
PGPokerHand::findNonJokerIndex(int32 rank, int32 nOfRank)
{
	int32 i;

	for (i = 0; i < getCount(); i++)
		if (!H_ISJOKER_AT(i) &&
			H_GETRANK_AT(i) == rank &&
			nOfRank-- <= 0)
			return i;

	return -1;
}

int32
PGPokerHand::findJokerIndex()
{
	int32 i;

	for (i = 0; i < getCount(); i++)
		if (H_ISJOKER_AT(i))
			return i;

	return -1;
}

int32
PGPokerHand::findNthHighestSuitIndex(int32 suit, int32 nOfSuit)
{
	int32 i;
	uint8 ndx[13];

	memset(ndx, 0xff, 13);

	for (i = 0; i < getCount(); i++)
		if (!H_ISJOKER_AT(i) &&
			H_GETSUIT_AT(i) == suit)
			ndx[COUNTNDX(H_GETRANK_AT(i))] = (uint8)(i & 0xff);

	for (i = 12; i >= 0; i--)
		if (ndx[i] != 0xff && nOfSuit-- <= 0)
			return ndx[i];

	return -1;
}

int32
PGPokerHand::findNthHighestOffSuitIndex(int32 suit, int32 nOfOffSuit)
{
	int32 i, j, cnt;
	uint8 ndx[MAXCARDS];	// ndx
	uint8 order[MAXCARDS];	// rank
	uint8 temp;
#	define SWAP(a,b)		temp=(a);(a)=(b);(b)=temp

	cnt = 0;

	for (i = 0; i < getCount(); i++)
		if (!H_ISJOKER_AT(i) &&
			H_GETSUIT_AT(i) != suit)
		{
			ndx[cnt] = (uint8)(i & 0xff);
			order[cnt] = H_GETRANK_AT(i);
			++cnt;
		}

	for (i = 0; i < cnt; i++)
		for (j = i + 1; j < cnt; j++)
			if (order[i] < order[j])
			{
				SWAP(order[i], order[j]);
				SWAP(ndx[i], ndx[j]);
			}

	if (nOfOffSuit < cnt)
		return ndx[nOfOffSuit];

	return -1;

#	undef SWAP
}

int32
PGPokerHand::findOffSuitIndex(int32 suit, int32 nOfOffSuit)
{
	int32 i;

	for (i = 0; i < getCount(); i++)
		if (!H_ISJOKER_AT(i) &&
			H_GETSUIT_AT(i) != suit &&
			nOfOffSuit-- <= 0)
			return i;

	return -1;
}


PGPokerHandRanks::PGPokerHandRanks(PGPokerHand* hand, int32* twocard,
	WhichRanks initWhich)
{
	this->hand = hand;
	twoCard[0] = twocard[0];
	twoCard[1] = twocard[1];

	loRank = rankLowHand();

	if (initWhich == BothRanks)
		hiRank = rankHighHand();
	else
		hiRank = -1;
}

int32
PGPokerHandRanks::rankLowHand()
{
	int32 rank1, rank2;

	rank1 = HPTR_GETRANK_AT(twoCard[0]);
	rank2 = HPTR_GETRANK_AT(twoCard[1]);

	// jokers only count as aces for the low hand
	//
	if (HPTR_ISJOKER_AT(twoCard[0]))	// do *not* use HPTR_ISJOKER(rank1)!
		rank1 = ACENDX;

	if (HPTR_ISJOKER_AT(twoCard[1]))	// do *not* use HPTR_ISJOKER(rank2)!
		rank2 = ACENDX;

	if (rank1 == rank2)
		return HR_PAIR;
	else
		return HR_HIGHCARD;
}

int32
PGPokerHandRanks::getLowNthHighCard(int32 n)
{
	int32 rank1, rank2;

	rank1 = HPTR_GETRANK_AT(twoCard[0]);
	rank2 = HPTR_GETRANK_AT(twoCard[1]);

	// jokers only count as aces for the low hand
	//
	if (HPTR_ISJOKER_AT(twoCard[0]))	// do *not* use HPTR_ISJOKER(rank1)!
		rank1 = ACENDX;

	if (HPTR_ISJOKER_AT(twoCard[1]))	// do *not* use HPTR_ISJOKER(rank2)!
		rank2 = ACENDX;

	if (n == 0)
		return rank1 >= rank2 ? rank1 : rank2;
	else
		return rank1 >= rank2 ? rank2 : rank1;
}

int32
PGPokerHandRanks::rankHighHand()
{
	int32 i;
	uint8 *probe, *first, *wild;
	bool8 flush = true;
	uint8 card, prev;

	if (!hand || (hand && hand->getCount() != PGPokerHand::MAXCARDS))
		return HR_HIGHCARD;

	memset(counts, 0, 13 + 1);

	prev = 0xff;
	for (i = 0; i < hand->getCount(); i++)
	{
		if (i == twoCard[0] || i == twoCard[1])	// skip the twocard hand
			continue;

		card = hand->getCardAt(i);

		if (!HPTR_ISJOKER(card))
		{
			++counts[COUNTNDX(HPTR_GETRANK(card))];

			if (prev != 0xff && flush)
				flush = (bool8)(HPTR_GETSUIT(card) == HPTR_GETSUIT(prev));

			prev = card;
		}
		else
			++counts[COUNTNDX(JOKERNDX)];
	}

	if (counts[COUNTNDX(JOKERNDX)])
	{
		// joker can only be used to complete straights, flushs,
		// or straight flushes...otherwise it's treated as an ace
		//

		// pair, 2 pairs, 3-of-a-kind, 4-of-a-kind, full house
		//
		if (memchr(counts, 4, 13))
		{
			if (counts[COUNTNDX(ACENDX)] == 4)
				return HR_5OFAKIND;
			else
				return HR_4OFAKIND;
		}

		if (memchr(counts, 3, 13))
		{
			// At this point we have a joker, 3 of something, and another card;
			// so, we can't have any pairs (counts can't contain a 2).
			//
			ASSERT(!memchr(counts, 2, 13));

			switch (counts[COUNTNDX(ACENDX)])
			{
				case 3: 			// 4 aces > full house
					return HR_4OFAKIND;
				case 1:
					return HR_FULLHOUSE;

				default:			// case 2 is impossible
					return HR_3OFAKIND;
			}
		}

		first = (uint8*)memchr(counts, 2, 13);
		if (first)						// at least one pair
		{
			if (RANKNDX(first - counts) == ACENDX)
				return HR_3OFAKIND;
			//
			// at this point we have a pair of non-aces
			//
			ASSERT(RANKNDX(first - counts) < ACENDX);

			switch (counts[COUNTNDX(ACENDX)])
			{
				case 2:
					return HR_FULLHOUSE;

				case 1:
					return HR_2PAIR;

				default:
				case 0:
					probe = (uint8*)memchr(first+1, 2, 13-(first-counts+1));
					if (probe)
						return HR_2PAIR;	// no aces in either

					return HR_PAIR;
			}
		}

		//
		// At this point the only kind of cards in the hand are singletons
		//

		// straights: insert wild in sequence after first non-wild card
		// check for straights, zap inserted wild
		//
		wild = (uint8*)0;
		first = (uint8*)memchr(counts, 1, 13);
		probe = first + 1;
		while (!wild && probe < counts + 13)
		{
			if (*probe == 0)
			{
				wild = probe;
				*probe = 1;
			}
			++probe;
		}
		probe = first - 1;
		while (!wild && probe >= counts)
		{
			if (*probe == 0)
			{
				wild = probe;
				*probe = 1;
			}
			--probe;
		}

		first = probe = (uint8*)memchr(counts, 1, 13);
		i = 0;
		while (probe < counts + 13)
			if (*probe++ == 1)
				i++;
			else
				break;

		if (i == 5)
		{
			*wild = 0;

			if (flush)
				if (first == counts + COUNTNDX(10))
					return HR_ROYALFLUSH;
				else
					return HR_STRAIGHTFLUSH;

			return HR_STRAIGHT;
		}

		*wild = 0;

		// ace-low straights
		//
		// At this point, code above has already caught joker in
		// low straights 2 thru 5, etc.  At this point we don't need to
		// distinguish between joker(ACE),2,3,4,5 and 2,3,4,5,joker(6).
		// Here, we only need to check for an actual Ace, and 3 other
		// cards <= a five card.
		//
		if (counts[COUNTNDX(ACENDX)])
		{
			// get first three cards and make sure they're <= five card
			// (guaranteed to exist since the hand must have MAXCARDS...
			// so, no need to check for null pointers)
			//
			first = (uint8*)memchr(counts, 1, 13);
			probe = (uint8*)memchr(first+1, 1, 13 - (first+1 - counts));

			if (first <= counts + COUNTNDX(5) &&
				probe <= counts + COUNTNDX(5) &&
				memchr(probe+1, 1, 13 - (probe+1 - counts)) <=
					counts + COUNTNDX(5))
			{
				if (flush)
					return HR_STRAIGHTFLUSH;

				return HR_STRAIGHT;
			}
		}

		if (flush)
			return HR_FLUSH;

		// pair with joker
		//
		if (counts[COUNTNDX(ACENDX)])
			return HR_PAIR;

		return HR_HIGHCARD;
	}
	else
	{
		// pair, 2 pairs, 3-of-a-kind, 4-of-a-kind, full house
		//
		if (memchr(counts, 4, 13))
			return HR_4OFAKIND;

		if (memchr(counts, 3, 13))
			if (memchr(counts, 2, 13))
				return HR_FULLHOUSE;
			else
				return HR_3OFAKIND;

		probe = (uint8*)memchr(counts, 2, 13);
		if (probe)
		{
			if (probe < counts+(13-1) &&
				memchr(probe+1, 2, 13 - (probe-counts+1)))
				return HR_2PAIR;
			else
				return HR_PAIR;
		}

		//
		// At this point the only kind of cards in the hand are singletons
		//

		// straights:  ace counts high or low;
		//			   ace,2,3,4,5 is 2nd highest straight,
		//			   just under 10,J,Q,K,A and just over 9,10,J,Q,K
		//
		first = probe = (uint8*)memchr(counts, 1, 13);
		i = 0;
		while (probe < counts + 13)
			if (*probe++ == 1)
				i++;
			else
				break;

		if (i == 5)
		{
			if (flush)
				if (first == counts + COUNTNDX(10))
					return HR_ROYALFLUSH;
				else
					return HR_STRAIGHTFLUSH;

			return HR_STRAIGHT;
		}

		if (i == 4 &&						// ace-low straights
			counts[COUNTNDX(ACENDX)] && first == counts)
		{
			if (flush)
				return HR_STRAIGHTFLUSH;

			return HR_STRAIGHT;
		}

		return flush ? HR_FLUSH : HR_HIGHCARD;
	}
}

int32
PGPokerHandRanks::getHighNthHighCard(int32 n)
{
	return getHighNthHighCard(n, hiRank);
}

int32
PGPokerHandRanks::getHighNthHighCard(int32 n, int32 r)
{
	uint8 *probe;

	if (counts[COUNTNDX(JOKERNDX)])
	{
		bool8 aceused;
		int32 result;
#		define MARK_JOKER_IF_NUMACE(cnt)									\
			aceused = (bool8)(counts[COUNTNDX(ACENDX)] == (cnt));			\
			if (aceused)													\
			{																\
				counts[COUNTNDX(ACENDX)] = 0;								\
				counts[COUNTNDX(JOKERNDX)] = -counts[COUNTNDX(JOKERNDX)];	\
			}
				
#		define UNMARK_JOKER_IF_NUMACE(cnt)									\
			if (aceused)													\
			{																\
				counts[COUNTNDX(ACENDX)] = (cnt);							\
				counts[COUNTNDX(JOKERNDX)] = -counts[COUNTNDX(JOKERNDX)];	\
			}

		switch (r)
		{
			case HR_5OFAKIND:	// the only 5 of a kind is 5 aces
				return ACENDX;

			case HR_4OFAKIND:
				if (n < 4)
				{
					if (counts[COUNTNDX(ACENDX)] == 3)
						return ACENDX;
					else
						return RANKNDX((uint8*)memchr(counts, 4, 13) - counts);
				}

				MARK_JOKER_IF_NUMACE(3);

				result = getHighNthHighCard(n, HR_HIGHCARD);

				UNMARK_JOKER_IF_NUMACE(3);
				return result;

			case HR_3OFAKIND:
				if (n < 3)
				{
					if (counts[COUNTNDX(ACENDX)] == 2)
						return ACENDX;
					else
						return RANKNDX((uint8*)memchr(counts, 3, 13) - counts);
				}

				MARK_JOKER_IF_NUMACE(2);

				result = getHighNthHighCard(n, HR_HIGHCARD);

				UNMARK_JOKER_IF_NUMACE(2);
				return result;
				
			case HR_FULLHOUSE:
				if (n < 3)
					return getHighNthHighCard(n, HR_3OFAKIND); 
				if (counts[COUNTNDX(ACENDX)] == 1)
					return ACENDX;
				return RANKNDX((uint8*)memchr(counts, 2, 13) - counts);

			case HR_2PAIR:
				if (n < 4)
				{
					probe = (uint8*)memchr(counts, 2, 13);

					if (n < 2)
					{
						uint8* probe2;

						if (counts[COUNTNDX(ACENDX)] == 1)
							return ACENDX;

						probe2 = (uint8*)memchr(probe+1,2,13-(probe-counts+1));

						return RANKNDX(probe2 - counts);
					}

					return RANKNDX(probe - counts);
				}

				aceused = (bool8)(counts[COUNTNDX(ACENDX)] == 1);

				MARK_JOKER_IF_NUMACE(1);

				result = getHighNthHighCard(n, HR_HIGHCARD);

				UNMARK_JOKER_IF_NUMACE(1);
				return result;

			case HR_PAIR:
				if (n < 2)
				{
					if (counts[COUNTNDX(ACENDX)] == 1)
						return ACENDX;

					return RANKNDX((uint8*)memchr(counts, 2, 13) - counts);
				}

				MARK_JOKER_IF_NUMACE(1);

				result = getHighNthHighCard(n, HR_HIGHCARD);

				UNMARK_JOKER_IF_NUMACE(1);
				return result;

			case HR_ROYALFLUSH:
			case HR_STRAIGHTFLUSH:
			case HR_STRAIGHT:
			{
				uint8 *first, *wild;
				int32 i;

				if (isHighAceLowStraight())
				{
					if (n < 4)
						return 5 - n;			// 5, then 4, then 3, then 2
					else
						return ACENDX;			// finally, ACE
				}

				//
				// At this point we can't have an ace-low straight.
				//
				// Insert wild in sequence after first non-wild card
				// get appropriately ranked card, zap inserted wild.
				//
				wild = (uint8*)0;
				first = (uint8*)memchr(counts, 1, 13);
				probe = first + 1;
				while (!wild && probe < counts + 13)
				{
					if (*probe == 0)
					{
						wild = probe;
						*probe = 1;
					}
					++probe;
				}
				probe = first - 1;
				while (!wild && probe >= counts)
				{
					if (*probe == 0)
					{
						wild = probe;
						*probe = 1;
					}
					--probe;
				}

				first = probe = (uint8*)memchr(counts, 1, 13);
				for (i = 4; i != n; i--)
					++probe;

				*wild = 0;
				return RANKNDX(probe - counts);
			}

			case HR_FLUSH:
			case HR_HIGHCARD:
			default:
			{
				int32 whichSingle = 4 - n;
				uint8* probe2;

				//
				// NOTE:  this is the *ONLY* case where counts[COUNT(JOKERNDX)]
				//		  is included in the search!
				//		  (i.e. use 14 instead of 13 in the memchr(..))
				//
				probe = (uint8*)memchr(counts, 1, 14);
				while (whichSingle--)
				{
					probe2 = (uint8*)memchr(probe+1, 1, 14 - (probe-counts+1));
					probe = probe2;
				}

				if (RANKNDX(probe - counts) == JOKERNDX)
					return ACENDX;

				return RANKNDX(probe - counts);
			}
		}
#		undef MARK_JOKER_IF_NUMACE
#		undef UNMARK_JOKER_IF_NUMACE
	}
	else
	{
		switch (r)
		{
			case HR_4OFAKIND:
				if (n < 4)
					return RANKNDX((uint8*)memchr(counts, 4, 13) - counts);
				return getHighNthHighCard(n, HR_HIGHCARD);

			case HR_3OFAKIND:
				if (n < 3)
					return RANKNDX((uint8*)memchr(counts, 3, 13) - counts);
				return getHighNthHighCard(n, HR_HIGHCARD);
				
			case HR_FULLHOUSE:
				if (n < 3)
					return getHighNthHighCard(n, HR_3OFAKIND); 
				return RANKNDX((uint8*)memchr(counts, 2, 13) - counts);

			case HR_2PAIR:
				if (n < 4)
				{
					probe = (uint8*)memchr(counts, 2, 13);

					if (n < 2)
					{
						uint8* probe2;

						probe2 = (uint8*)memchr(probe+1,2,13-(probe-counts+1));

						return RANKNDX(probe2 - counts);
					}

					return RANKNDX(probe - counts);
				}
				return getHighNthHighCard(n, HR_HIGHCARD);

			case HR_PAIR:
				if (n < 2)
					return RANKNDX((uint8*)memchr(counts, 2, 13) - counts);
				return getHighNthHighCard(n, HR_HIGHCARD);

			case HR_ROYALFLUSH:
			case HR_STRAIGHTFLUSH:
			case HR_STRAIGHT:
			{
				int32 i;

				if (isHighAceLowStraight())
				{
					if (n < 4)
						return 5 - n;			// 5, then 4, then 3, then 2
					else
						return ACENDX;			// finally, ACE
				}

				probe = counts + 13;
				i = 0;
				do
				{
					while (*(--probe) == 0)
						/* search */ ;

				} while ((i++) < n);    // kharmon.  09-25-06.  #15537.  Having a pre-increment on i results in the first card getting returned for n==0 AND n==1.

				return RANKNDX(probe - counts);
			}

			//
			// can't have 5 of a kind without a joker...so use default
			//
			case HR_FLUSH:
			case HR_HIGHCARD:
			default:
			{
				int32 whichSingle = 4 - n;
				uint8* probe2;

				probe = (uint8*)memchr(counts, 1, 13);
				while (whichSingle--)
				{
					probe2 = (uint8*)memchr(probe+1, 1, 13 - (probe-counts+1));
					probe = probe2;
				}

				return RANKNDX(probe - counts);
			}
		}
	}
}

bool8
PGPokerHandRanks::isHighAceLowStraight()
{
	// this code should only be called when then High-hand rank is a straight
	// of some sort
	//
	ASSERT(hiRank == HR_ROYALFLUSH ||
		hiRank == HR_STRAIGHTFLUSH ||
		hiRank == HR_STRAIGHT);

	if (counts[COUNTNDX(JOKERNDX)])
	{
		int32 i;
		int32 numLessEq5;

		numLessEq5 = 0;

		for (i = COUNTNDX(2); i <= COUNTNDX(5); i++)
			if (counts[i])
				++numLessEq5;

		return (bool8)((numLessEq5 == 3 && counts[COUNTNDX(ACENDX)]) ||
			numLessEq5 == 4);
	}
	else
		return (bool8)(counts[COUNTNDX(ACENDX)] && counts[COUNTNDX(5)]);
}

int32
PGPokerHandRanks::compareLowRanks(PGHandRanks* iother)
{
	PGPokerHandRanks* other = (PGPokerHandRanks*)iother;

	if (loRank < other->loRank)
		return -1;
	else if (loRank > other->loRank)
		return 1;
	else
	{
		int32 i, result;
		int32 mine, his;

		for (result = i = 0; i < 2; i++)
		{
			mine = getLowNthHighCard(i);
			his = other->getLowNthHighCard(i);
			
			if (mine < his)
				return -1;
			else if (mine > his)
				return 1;
		}

		return 0;
	}
}

int32
PGPokerHandRanks::compareHighRanks(PGHandRanks* iother)
{	
	PGPokerHandRanks* other = (PGPokerHandRanks*)iother;

	if (hiRank < other->hiRank)
		return -1;
	else if (hiRank > other->hiRank)
		return 1;
	else
	{
		int32 result, i, mine, his;

		// ace-low straights rank as 2nd highest straights (which throws a
		// wrench into the generic straight-comparison code)
		//
		result = compareAceLowStraights(other);
		if (!result)
		{
			for (i = 0; i < 5; i++)
			{
				mine = getHighNthHighCard(i);
				his = other->getHighNthHighCard(i);

				if (mine < his)
				{
					result = -1;
					break;
				}
				else if (mine > his)
				{
					result = 1;
					break;
				}
			}
		}

		return result;
	}
}

int32
PGPokerHandRanks::compareAceLowStraights(PGPokerHandRanks* other)
{
	// this code should only be called when then High-hand ranks are equal
	//
	ASSERT(hiRank == other->hiRank);

	if (hiRank == HR_STRAIGHTFLUSH ||
		hiRank == HR_STRAIGHT)
	{
		//
		// check for special straight ordering here
		// straight ranks: AKQJT > A2345 > KQJT9
		//
		// (don't need to check royal flushes since they're always highest
		// and two royal flushes will always push)
		//
		int32 mineIsAceLow, hisIsAceLow;
		
		mineIsAceLow = isHighAceLowStraight();
		hisIsAceLow = other->isHighAceLowStraight();

		if (mineIsAceLow != hisIsAceLow)
		{
			int32 hiCard;

			if (mineIsAceLow)
			{
				hiCard = other->getHighNthHighCard(0);

				return (hiCard == ACENDX) ? -1 : 1;
			}
			else			// other has ace-low straight
			{
				hiCard = getHighNthHighCard(0);

				return (hiCard == ACENDX) ? 1 : -1;
			}
		}
		//
		// else both are ace low straights (push)
		// or neither are ace low straights (use normal straight-comparison)
		//
	}

	return 0;
}

int32
PGPokerHandRanks::getHighJokerAsRank()
{
	int32 result = 0xff;		// no joker

	ASSERT(hiRank != -1);		// only valid if we've ranked the high hand

	if (counts[COUNTNDX(JOKERNDX)])
	{
		if (hiRank == HR_ROYALFLUSH		||
			hiRank == HR_STRAIGHTFLUSH	||
			hiRank == HR_STRAIGHT)
		{
			if (isHighAceLowStraight())
			{
				uint8* which = (uint8*)memchr(counts, 0, 4);  // check 2 thru 5

				if (which)
					result = RANKNDX(which - counts);
				else
					result = ACENDX;
			}
			else
			{
				uint8 *wild, *first, *probe;

				wild = (uint8*)0;
				first = (uint8*)memchr(counts, 1, 13);
				probe = first + 1;
				while (!wild && probe < counts + 13)
				{
					if (*probe == 0)
					{
						wild = probe;
						*probe = 1;
					}
					++probe;
				}
				probe = first - 1;
				while (!wild && probe >= counts)
				{
					if (*probe == 0)
					{
						wild = probe;
						*probe = 1;
					}
					--probe;
				}

				*wild = 0;

				result = RANKNDX(wild - counts);
			}
		}
		else
			result = ACENDX;
	}

	return result;
}

PGTilesHandRanks::PGTilesHandRanks(PGTilesHand* hand, int32* twocard,
	WhichRanks initWhich)
{
	loCards[0] = HPTR_GETRANK_AT(twocard[0]);
	loCards[1] = HPTR_GETRANK_AT(twocard[1]);

	for(int32 i=0, j = 0;i<4;i++)
	{
		if (i == twocard[0] || 
			i == twocard[1])
			continue;
		
		hiCards[j++] = HPTR_GETRANK_AT(i);
	}

	loRank = rankHand(loCards, &bestLowHandCard);

	if (initWhich == BothRanks)
	{
		hiRank = rankHand(hiCards, &bestHighHandCard);
	}
	else
	{
		bestHighHandCard = HR_ZERO;
		hiRank = HR_ZERO;
	}
}

int32
PGTilesHandRanks::compareLowRanks(PGHandRanks* iother)
{
	PGTilesHandRanks* other = (PGTilesHandRanks*)iother;

	if (loRank < other->loRank)
		return -1;
	else if (loRank > other->loRank)
		return 1;
	else
	{
		// don't check best card when zero
		if (loRank == HR_ZERO)
			return 0;

		// lowest best card wins
		if (getBestLowHandCard() < other->getBestLowHandCard())
			return -1;
		else if (getBestLowHandCard() > other->getBestLowHandCard())
			return 1;

		return 0;
	}
}

int32
PGTilesHandRanks::compareHighRanks(PGHandRanks* iother)
{	
	PGTilesHandRanks* other = (PGTilesHandRanks*)iother;

	if (hiRank < other->hiRank)
		return -1;
	else if (hiRank > other->hiRank)
		return 1;
	else
	{	
		// don't check best card when zero
		if (hiRank == HR_ZERO)
			return 0;

		// lowest best card wins
		if (getBestLowHandCard() < other->getBestLowHandCard())
			return -1;
		else if (getBestLowHandCard() > other->getBestLowHandCard())
			return 1;

		return 0;
	}
}

bool8
PGTilesHand::isSplitValid(int32 twocard[2])
{
	PGTilesHandRanks ranks(this, twocard);

	if (ranks.getLowRank() < ranks.getHighRank())
		return true;
	else if (ranks.getLowRank() > ranks.getHighRank())
		return false;
	else
	{
		// don't check best card when zero
		if (ranks.getLowRank() == PGTilesHandRanks::HR_ZERO &&
			ranks.getHighRank() == PGTilesHandRanks::HR_ZERO )
			return true;

		// lowest best card wins
		if (ranks.getBestLowHandCard() < ranks.getBestHighHandCard())
			return true;
		else if (ranks.getBestLowHandCard() > ranks.getBestHighHandCard())
			return false;

		return true;
	}
}



PGTilesHandRanks::HandRanks
PGTilesHandRanks::rankHand(int32 twoCards[2], int32* bestCard)
{
	HandRanks rank1 = tileRank(twoCards[0]);
	HandRanks rank2 = tileRank(twoCards[1]);

	*bestCard = 0;

	if (rank1 == rank2)		//pair
		return rank1;
	else if ((rank1 == HR_TEEN || rank2 == HR_TEEN	//wong
		|| rank1 == HR_DAY || rank2 == HR_DAY)
			&& handPipCount(twoCards) == 9)
	{
		return HR_WONG;
	
	}
	else if ((rank1 == HR_TEEN || rank2 == HR_TEEN	//gong
		|| rank1 == HR_DAY || rank2 == HR_DAY)
			&& handPipCount(twoCards) == 8)
	{
		return HR_GONG;
	}
	else
	{
		*bestCard = MIN(rank1, rank2);

		return (HandRanks)(HR_ZERO + handPipCount(twoCards));
	}
}


int32
PGTilesHandRanks::handPipCount(int32 twoCards[2])
{
	int32 rank1 = twoCards[0];
	int32 rank2 = twoCards[1];
	
	int32 pipcount1 = pipCount(rank1);
	int32 pipcount2 = pipCount(rank2);

	int32 pipcount = (pipcount1 + pipcount2);
	int32 wildpipcount = pipcount;

	if (tileRank(rank1) == 0 ||
		tileRank(rank2) == 0)
		wildpipcount += 3;

	if (tileRank(rank1) == 1 ||
		tileRank(rank2) == 1)
		wildpipcount -= 3;

	while (pipcount > 9)
		pipcount -= 10;

	while (wildpipcount > 9)
		wildpipcount -= 10;

	return MAX(pipcount, wildpipcount); 
}

int32
PGTilesHandRanks::pipCount(int32 rank)
{
	switch (rank)
	{
		case 0:
			return 12;
		case 1:
			return 12;
		case 2:
			return 11;
		case 3:
			return 11;
		case 4:
			return 10;
		case 5:
			return 10;
		case 6:
			return 9;
		case 7:
			return 8;
		case 8:
			return 7;
		case 9:
			return 7;
		case 10:
			return 10;
		case 11:
			return 10;
		case 12:
			return 9;
		case 13:
			return 8;
		case 14:
			return 7;
		case 15:
			return 6;
		case 16:
			return 6;
		case 17:
			return 8;
		case 18:
			return 8;
		case 19:
			return 7;
		case 20:
			return 6;
		case 21:
			return 5;
		case 22:
			return 6;
		case 23:
			return 6;
		case 24:
			return 5;
		case 25:
			return 4;
		case 26:
			return 4;
		case 27:
			return 4;
		case 28:
			return 4;
		case 29:
			return 3;
		case 30:
			return 2;
		case 31:
			return 2;

		default:
			ASSERT ( false );
	}

	return 0;
}

PGTilesHandRanks::HandRanks
PGTilesHandRanks::tileRank(int32 rank)
{
	switch (rank)
	{
		case 29:
		case 20:
			return HR_GEE_JOON;

		case 0:
		case 1:
			return HR_TEEN;

		case 30:
		case 31:
			return HR_DAY;

		case 17:
		case 18:
			return HR_YUN;

		case 25:
		case 26:
			return HR_GOR;

		case 10:
		case 11:
			return HR_MOOY;

		case 22:
		case 23:
			return HR_CHONG;

		case 27:
		case 28:
			return HR_BON;

		case 2:
		case 3:
			return HR_FOO;

		case 4:
		case 5:
			return HR_PING;

		case 8:
		case 9:
			return HR_TILT;

		case 15:
		case 16:
			return HR_LOOK;

		case 6:
		case 12:
			return HR_CHOP_GOW;

		case 7:
		case 13:
			return HR_CHOP_BOT;

		case 19:
		case 14:
			return HR_CHOP_CHIT;

		case 24:
		case 21:
			return HR_CHOP_NG;

		default:
			ASSERT( false );
	}

	return HR_ZERO;
}
